const rewire = require('rewire')
const state = rewire('../state.cjs')
const dbutils = rewire('../common/dbutils.cjs')
const {uuid} = rewire('../common/utils.cjs')
const {parallelIter, whenNoError} = rewire('../common/combinator.cjs')
const R = require('ramda')
const {getLogger} = rewire('../common/logger.cjs')
const logger = getLogger('fnstate')

/**
 *
CREATE TABLE `fnstate` (
  `id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `fnkey` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `dataitem` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
  `status` smallint DEFAULT NULL COMMENT '状态：0未处理，1已处理，2跳过',
  `ctime` bigint DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `fnkey` (`fnkey`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 */

// 待处理
const FnStateTODO = 0
// 完成
const FnStateDone = 1
// 跳过
const FnStateSkip = 2

// 创建一个可保存状态，可继续的方法。
// 逻辑：先保存所有的数据，然后遍历处理每个数据，如果成功执行则从数据库中删除这个数据。循环这个过程。
function wrapFnState(key, n, iterFn, iterOnError) {
  if(key == null || n <= 0) {
    throw new Error("参数错误")
  }
  return async function(data) {
    if (data.length <= 0) {
      throw new Error("data长度有问题")
    }

    // 查看数据库
    let dbdata =  await state.getDb()
      .select('*')
      .from('fnstate')
      .where('fnkey', key)
      .queryList()

    // 如果数据库没有记录，那就保存到数据库中
    if (dbdata.length <= 0) {
      logger.info("开始全新任务:" + key)
      const list = data.map(item => { return {id: uuid(), fnkey: key, dataitem: JSON.stringify(item), status: FnStateTODO, ctime: new Date().getTime()}})
      // 将数据保存在fnstate表中
      await dbutils.insertAll(
        state.getDb(), 'fnstate',
        list
      )
      dbdata = list
    }
    // 如果数据库已存在记录，则认为要继续执行，从而忽略传递过来的data数据，并过滤掉数据库中已处理的数据。
    else {
      dbdata = R.filter(item => item['status'] == FnStateTODO, dbdata)
      // 如果是空的，说明流程已经执行完毕，直接返回
      if (R.isEmpty(dbdata)) {
        logger.info("该任务早已完结，无需再执行:" + key)
        return
      }
      else {
        logger.info("继续原有任务:" + key)
      }
    }

    // 任务遍历函数，当出现异常时，更想数据库状态，并回调函数
    let taskFn = whenNoError(
      async i => {
        const item = dbdata[i]
        await iterFn(JSON.parse(item['dataitem']))
      },
      // 成功回调
      async (rs, args) => {
        const i = args[0]
        const item = dbdata[i]
        // 更新数据库状态
        await state.getDb()
          .update("fnstate")
          .column("status", FnStateDone)
          .where('id', item['id'])
          .execute();
      },
      // 失败回调
      async (e, args) => {
        const i = args[0]
        const item = dbdata[i]
        // 更新数据库状态
        await state.getDb()
          .update("fnstate")
          .column("status", FnStateSkip)
          .where('id', item['id'])
          .execute();
        
        // 回调函数
        await iterOnError(e, item)
      }
    )
    // 执行
    await parallelIter(n, taskFn, R.range(0, dbdata.length))
  }
}

// 获取任务进度
async function getProgress(key) {
  let total =  await state.getDb()
      .select('count(1)')
      .from('fnstate')
      .where('fnkey', key)
      .queryValue()
  let todo = await state.getDb()
    .select('count(1)')
    .from('fnstate')
    .where('fnkey', key)
    .where('status', 0)
    .queryValue()
  let done = await state.getDb()
    .select('count(1)')
    .from('fnstate')
    .where('fnkey', key)
    .where('status', 1)
    .queryValue()
  let skip = await state.getDb()
    .select('count(1)')
    .from('fnstate')
    .where('fnkey', key)
    .where('status', 2)
    .queryValue()
  return {total, todo, done, skip, rate: ((done + skip) / total * 100).toFixed(2) + "%"}
}

module.exports = {wrapFnState, getProgress}